iT邦幫忙

2024 iThome 鐵人賽

DAY 14
0
自我挑戰組

ABAP 基礎30天學習筆記系列 第 14

Day14_用封裝確保一致性

  • 分享至 

  • xImage
  •  

原文連結:Using Encapsulation to Ensure Consistency

資料封裝

在物件導向的程式中,當一個物件代表了現實世界中的物件,像是員工、汽車及飛機等,會有許多描述該物件的屬性,像代表承運公司的carrier_id以及航班編號flight_number

一般而言,這些與現實有固定組合的值不應被隨意修改,必須要有額外方法阻擋及檢查修改是否被允許。

例如下例中,在lcl_connection這個class裡,我們可以把carrier_idconnection_id設成private或是唯讀值,改用呼叫函式set_attributes來替屬性賦值。

*  取消原始直接對屬性賦值的方式
*  connection->carrier_id     = 'XY'.   "註解掉"
*  connection->connection_id  = '0001'. "註解掉"

* 改用呼叫函式來替屬性賦值
connection->set_attributes(...).

這個概念稱為資料封裝。重要資訊僅能透過原始物件本身管理,可以確保沒有外部程式能竄改物件或繞過一致性檢查,這也是物件導向的一大優點。推薦在適當處盡可能的用封裝來保護程式碼!

在class定義中封裝屬性

CLASS <class_name> DEFINTIION.
    PUBLIC SECTION.
     (CLASS-)DATA <public_attribute>  TYPE <type>.
     "1. 將屬性設成唯讀"
     (CLASS-)DATA <read_only_attribute>  TYPE <type> READ-ONLY.
        ...
    "2. 將屬性放在PRIVATE區段"
    PRIVATE SECTION.
     (CLASS-)DATA <private_attribute>  TYPE <type>.
        ...
ENDCLASS.     

方式一:
在public變數後加上READ-ONLY,表示為唯讀參數,僅能供外部讀取而無法編輯。

方式二:
設成private變數,外部無法讀取和編輯。可以按下Ctrl + 1後選擇屬性來快速設成private變數

直接封裝的潛在風險

當把屬性設成private或唯讀,可以確保只能用指定的方法來修改屬性,如在下面範例中就是set_attributes()。然而,仍有兩個潛在的狀況將導致與現實不一致的風險:

DATA connection1 TYPE REF TO lcl_connection.
DATA connection2 TYPE TABLE OF REF TO lcl_connection.

" 風險1. 未呼叫set_attributes( )"
connection1 = NEW #( ).

" 風險2. set_attributes( )被多次呼叫及修改"
connection2 = NEW #( ).
connection2->set_attributes(
            carrier_id     = 'LH.
            connection_id  = '0400'.
            )
connection2->set_attributes(
            carrier_id     = 'AA.
            connection_id  = '0017'.
            )

風險一. 由於無法強制程式一定要在實例裡呼叫set_attributes( ),若沒主動設定屬性,這時connection1的屬性會被填入**該型別(TYPE)**的初始預設值。

風險二. 由於可以在同一個實例中多次呼叫set_attributes( ),將造成connection2的屬性賦值後可能被多次修改。

建構函數

為了避免使用預設值及多次更改等風險,這時可以用建構函數(constructor method):

CLASS <class_name> DEFINTIION.
    PUBLIC SECTION.
    
     "建構函式宣告於PUBLIC區段中"
     METHODS constructor
        IMPORTING <input_1> TYPE <type>
                  <input_2> TYPE <type> DEFAULT <val>
                  ...
       RASING     <exception1>
                  <exception2>
                  ...
ENDCLASS.     

當一個類別存在建構函式,將在實體化同時必定自動呼叫該建構函式,並只會呼叫一次。
在語法上,建構函式擁有以下特徵:

  • 在實體化區段中,是一個public的方法,名為constructor
  • 可能有導入參數,用來取得初始值。
  • 可能有例外參數,用於警示可能發生的例外

注意,在ABAP中,同一個類別只能有一個建構函式。

定義建構子

* Definition內定義建構子
METHODS constructor
    IMPORTING
      carrier_id    TYPE /dmo/carrier_id
      connection_id TYPE /dmo/connection_id
* Implementation區段內實體化建構子
METHOD constructor.
    
      me->carrier_id    = carrier_id.
      me->connection_id = connection_id.
      
ENDMETHOD.

在上述範例中展示了 lcl_connection這個類別的建構子,由於建構函式中的參數有著與導入參數相同的名稱,在這裡使用ME對兩個參數做出區別。

建構函數的額外設定

下面範例為建構函數的一些額外設定,可以看需求添加:

Definition內定義建構子
METHODS constructor
    IMPORTING
      carrier_id    TYPE /dmo/carrier_id
      connection_id TYPE /dmo/connection_id
    "例外事件定義"
    RASING
      cx_abap_invalid_value.
METHOD constructor.
    "檢查是否有被設置初始值"
    IF carrier_id IS INITIAL OR connection_id IS INITIAL.
       RASING EXCEPTION TYPE cx_abap_invalid_value.
    ENDIF.
    
    me->carrier_id    = carrier_id.
    me->connection_id = connection_id.
    
    #計算實體數量
    conn_counter = conn_counter + 1.
ENDMETHOD.
    
  1. 初始值檢查
    為了避免建立具有有初始值的實例,在實例化時加入例外檢查。

  2. 用conn_countor計算實體數量
    由於建構函數有著"僅在實例化時呼叫唯一的一次"特性,且靜態屬性的值是跟著class而非實體,在這裡可以設一個特殊的靜態屬性conn_countor來計數,每當該類別產生一個新實體,conn_countor就會遞增一次,可以用來計算同一類別實體的總數。

實體化建構函式 & 型別檢查

* Definition內定義建構子
METHODS constructor
    IMPORTING
      carrier_id    TYPE /dmo/carrier_id
      connection_id TYPE /dmo/connection_id
    "例外事件定義"
    RASING
      cx_abap_invalid_value.
      ...
* 於NEW#()建立實體時
DATA connection TYPE REF TO lcl_connection.

TRY.
    "用表達式NEW #()直接導入參數"
    connection = NEW #( carrier_id  = 'LH'
                        connection_id = '0400'    ).

CATCH cx_abap_invalid_value.
    ...
ENDTRY.

延續上個class的設定,在正式透過NEW#()設定實體時,建構函數會被自動呼叫,一旦存在導入參數數(importing parameters),在NEW#()中就需提供初始值。語法上跟把參數傳遞到普通方法中一樣。

另外,為了避免建構發生例外狀況,可以加入例外檢查導入參數的型別是否與預設值一致。

註:建構子只能傳入導入參數,EXPORTING關鍵字是不被允許的

靜態建構函數

動態的建構函數只要建立實體時都會被呼叫一次,但有時你只需要對整個class執行一次設定,這時就需要靜態建構子,也可稱為類別建構子。

靜態建構子則只在該class於第一次被尋址到時,於此時呼叫一次。

第一次被尋址的情況可能發生於:

  • 首次實體化該類別
  • 首次呼叫一個靜態方法
  • 首次存取一個public的靜態屬性

常見案例會是動態地初始化了一個沒有初始值的靜態屬性,因此在實體化前,最好先呼叫靜態建構子來賦值。

CLASS <class_name> DEFINITION.
    PUBLIC SECTION.
    
        CLASS-METHODS class_constructor.
ENDCLASS.

在語法上,靜態建構子有以下特色:

  • public 中的靜態方法(前綴CLASS-METHODS),並有著保留名稱class_constructor
  • 沒有任何參數及例外

明天正式進入資料庫的環節!


上一篇
Day13_定義與呼叫方法
下一篇
Day15_ABAP Table概述
系列文
ABAP 基礎30天學習筆記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言